/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.configuration.event; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; /** * <p> * A base class for objects that can generate configuration events. * </p> * <p> * This class implements functionality for managing a set of event listeners * that can be notified when an event occurs. It can be extended by * configuration classes that support the event machanism. In this case these * classes only need to call the <code>fireEvent()</code> method when an event * is to be delivered to the registered listeners. * </p> * <p> * Adding and removing event listeners can happen concurrently to manipulations * on a configuration that cause events. The operations are synchronized. * </p> * <p> * With the <code>detailEvents</code> property the number of detail events can * be controlled. Some methods in configuration classes are implemented in a way * that they call other methods that can generate their own events. One example * is the <code>setProperty()</code> method that can be implemented as a * combination of <code>clearProperty()</code> and <code>addProperty()</code>. * With <code>detailEvents</code> set to <b>true</b>, all involved methods * will generate events (i.e. listeners will receive property set events, * property clear events, and property add events). If this mode is turned off * (which is the default), detail events are suppressed, so only property set * events will be received. Note that the number of received detail events may * differ for different configuration implementations. * <code>{@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}</code> * for instance has a custom implementation of <code>setProperty()</code>, * which does not generate any detail events. * </p> * <p> * In addition to "normal" events, error events are supported. Such * events signal an internal problem that occurred during access of properties. * For them a special listener interface exists: * <code>{@link ConfigurationErrorListener}</code>. There is another set of * methods dealing with event listeners of this type. The * <code>fireError()</code> method can be used by derived classes to send * notifications about errors to registered observers. * </p> * * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a> * @version $Id: EventSource.java 561230 2007-07-31 04:17:09Z rahul $ * @since 1.3 */ public class EventSource { /** A collection for the registered event listeners. */ private Collection listeners; /** A collection for the registered error listeners.*/ private Collection errorListeners; /** A counter for the detail events. */ private int detailEvents; /** * Creates a new instance of <code>EventSource</code>. */ public EventSource() { initListeners(); } /** * Adds a configuration listener to this object. * * @param l the listener to add */ public void addConfigurationListener(ConfigurationListener l) { doAddListener(listeners, l); } /** * Removes the specified event listener so that it does not receive any * further events caused by this object. * * @param l the listener to be removed * @return a flag whether the event listener was found */ public boolean removeConfigurationListener(ConfigurationListener l) { return doRemoveListener(listeners, l); } /** * Returns a collection with all configuration event listeners that are * currently registered at this object. * * @return a collection with the registered * <code>ConfigurationListener</code>s (this collection is a snapshot * of the currently registered listeners; manipulating it has no effect * on this event source object) */ public Collection getConfigurationListeners() { return doGetListeners(listeners); } /** * Removes all registered configuration listeners. */ public void clearConfigurationListeners() { doClearListeners(listeners); } /** * Returns a flag whether detail events are enabled. * * @return a flag if detail events are generated */ public boolean isDetailEvents() { synchronized (listeners) { return detailEvents > 0; } } /** * Determines whether detail events should be generated. If enabled, some * methods can generate multiple update events. Note that this method * records the number of calls, i.e. if for instance * <code>setDetailEvents(false)</code> was called three times, you will * have to invoke the method as often to enable the details. * * @param enable a flag if detail events should be enabled or disabled */ public void setDetailEvents(boolean enable) { synchronized (listeners) { if (enable) { detailEvents++; } else { detailEvents--; } } } /** * Adds a new configuration error listener to this object. This listener * will then be notified about internal problems. * * @param l the listener to register (must not be <b>null</b>) * @since 1.4 */ public void addErrorListener(ConfigurationErrorListener l) { doAddListener(errorListeners, l); } /** * Removes the specified error listener so that it does not receive any * further events caused by this object. * * @param l the listener to remove * @return a flag whether the listener could be found and removed * @since 1.4 */ public boolean removeErrorListener(ConfigurationErrorListener l) { return doRemoveListener(errorListeners, l); } /** * Removes all registered error listeners. * * @since 1.4 */ public void clearErrorListeners() { doClearListeners(errorListeners); } /** * Returns a collection with all configuration error listeners that are * currently registered at this object. * * @return a collection with the registered * <code>ConfigurationErrorListener</code>s (this collection is a * snapshot of the currently registered listeners; it cannot be manipulated) * @since 1.4 */ public Collection getErrorListeners() { return doGetListeners(errorListeners); } /** * Creates an event object and delivers it to all registered event * listeners. The method will check first if sending an event is allowed * (making use of the <code>detailEvents</code> property), and if * listeners are registered. * * @param type the event's type * @param propName the name of the affected property (can be <b>null</b>) * @param propValue the value of the affected property (can be <b>null</b>) * @param before the before update flag */ protected void fireEvent(int type, String propName, Object propValue, boolean before) { Collection listenersToCall = null; synchronized (listeners) { if (detailEvents >= 0 && listeners.size() > 0) { // Copy listeners to another collection so that manipulating // the listener list during event delivery won't cause problems listenersToCall = new ArrayList(listeners); } } if (listenersToCall != null) { ConfigurationEvent event = createEvent(type, propName, propValue, before); for (Iterator it = listenersToCall.iterator(); it.hasNext();) { ((ConfigurationListener) it.next()).configurationChanged(event); } } } /** * Creates a <code>ConfigurationEvent</code> object based on the passed in * parameters. This is called by <code>fireEvent()</code> if it decides * that an event needs to be generated. * * @param type the event's type * @param propName the name of the affected property (can be <b>null</b>) * @param propValue the value of the affected property (can be <b>null</b>) * @param before the before update flag * @return the newly created event object */ protected ConfigurationEvent createEvent(int type, String propName, Object propValue, boolean before) { return new ConfigurationEvent(this, type, propName, propValue, before); } /** * Creates an error event object and delivers it to all registered error * listeners. * * @param type the event's type * @param propName the name of the affected property (can be <b>null</b>) * @param propValue the value of the affected property (can be <b>null</b>) * @param ex the <code>Throwable</code> object that caused this error event * @since 1.4 */ protected void fireError(int type, String propName, Object propValue, Throwable ex) { Collection listenersToCall = null; synchronized (errorListeners) { if (errorListeners.size() > 0) { // Copy listeners to another collection so that manipulating // the listener list during event delivery won't cause problems listenersToCall = new ArrayList(errorListeners); } } if (listenersToCall != null) { ConfigurationErrorEvent event = createErrorEvent(type, propName, propValue, ex); for (Iterator it = listenersToCall.iterator(); it.hasNext();) { ((ConfigurationErrorListener) it.next()).configurationError(event); } } } /** * Creates a <code>ConfigurationErrorEvent</code> object based on the * passed in parameters. This is called by <code>fireError()</code> if it * decides that an event needs to be generated. * * @param type the event's type * @param propName the name of the affected property (can be <b>null</b>) * @param propValue the value of the affected property (can be <b>null</b>) * @param ex the <code>Throwable</code> object that caused this error * event * @return the event object * @since 1.4 */ protected ConfigurationErrorEvent createErrorEvent(int type, String propName, Object propValue, Throwable ex) { return new ConfigurationErrorEvent(this, type, propName, propValue, ex); } /** * Overrides the <code>clone()</code> method to correctly handle so far * registered event listeners. This implementation ensures that the clone * will have empty event listener lists, i.e. the listeners registered at an * <code>EventSource</code> object will not be copied. * * @return the cloned object * @throws CloneNotSupportedException if cloning is not allowed * @since 1.4 */ protected Object clone() throws CloneNotSupportedException { EventSource copy = (EventSource) super.clone(); copy.initListeners(); return copy; } /** * Adds a new listener object to a listener collection. This is done in a * synchronized block. The listener must not be <b>null</b>. * * @param listeners the collection with the listeners * @param l the listener object */ private static void doAddListener(Collection listeners, Object l) { if (l == null) { throw new IllegalArgumentException("Listener must not be null!"); } synchronized (listeners) { listeners.add(l); } } /** * Removes an event listener from a listener collection. This is done in a * synchronized block. * * @param listeners the collection with the listeners * @param l the listener object * @return a flag whether the listener could be found and removed */ private static boolean doRemoveListener(Collection listeners, Object l) { synchronized (listeners) { return listeners.remove(l); } } /** * Removes all entries from the given list of event listeners. * * @param listeners the collection with the listeners */ private static void doClearListeners(Collection listeners) { synchronized (listeners) { listeners.clear(); } } /** * Returns an unmodifiable snapshot of the given event listener collection. * * @param listeners the collection with the listeners * @return a snapshot of the listeners collection */ private static Collection doGetListeners(Collection listeners) { synchronized (listeners) { return Collections.unmodifiableCollection(new ArrayList(listeners)); } } /** * Initializes the collections for storing registered event listeners. */ private void initListeners() { listeners = new LinkedList(); errorListeners = new LinkedList(); } }